package adapter

import (
	"context"
	"github.com/hyperway/pkg/component"
	"github.com/hyperway/pkg/config"
	"github.com/hyperway/pkg/constant"
	"github.com/hyperway/pkg/helper"
	"github.com/hyperway/pkg/logger"
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"github.com/labstack/gommon/random"
	"net/http"
	"strings"
)

func NewWebListener(config *config.Configuration) component.WebListener {

	server := echo.New()

	name := config.GetString("name")
	address := config.GetString("address")
	port := config.GetString("port")

	webListener := &EchoListener{
		name:    name,
		address: address,
		port:    port,
		server:  server,
	}
	webListener.SetErrorHandler(defaultErrorHandler)
	webListener.SetNotFoundHandlerFunc(defaultNotFoundHandler)
	server.Pre(func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			id := webListener.XRequestID(c)
			webContext := NewEchoContext(webListener, c, id)
			c.Set(constant.RequestScopeContextKey, webContext)
			defer func() {
				if err := recover(); err != nil {
					logger.TraceId(id).Errorw("panic", "error", err)
					c.JSON(500, "出错了")
				}
			}()
			return next(c)
		}
	})

	// 设置BodyLimit
	if bodyLimit := config.GetString("features.body_limit"); bodyLimit != "" {
		logger.Infof("Server(id:%s) 开启BodyLimit限制, body-limit: size= %s", webListener.name, bodyLimit)
		server.Pre(middleware.BodyLimit(bodyLimit))
	}

	// CORS（是否开启支持跨域访问特性）
	if enabled := config.GetBool("features.cors_enable"); enabled {
		logger.Infof("Server(id:%s) 开启跨域访问", webListener.name)
		server.Pre(middleware.CORS())
	}

	// CSRF（是否开启检查跨站请求伪造特性）
	if enabled := config.GetBool("features.csrf_enable"); enabled {
		logger.Infof("Server(id:%s) 开启CSRF", webListener.name)
		server.Pre(middleware.CSRF())
	}

	server.HideBanner = true
	server.Use(middleware.Recover())

	return webListener
}

type EchoListener struct {
	name            string
	address         string
	port            string
	server          *echo.Echo
	notFoundHandler component.RequestHandlerFunc
}

func (e *EchoListener) Name() string {
	return e.name
}

func (e *EchoListener) Start() error {
	logger.Infow("web server started", "name", e.name, "address", e.address, "port", e.port)
	e.server.Start(e.address + ":" + e.port)
	return nil
}

func (e *EchoListener) SetErrorHandler(errorHandler component.ErrorHandlerFunc) {
	e.server.HTTPErrorHandler = func(err error, c echo.Context) {
		if helper.IsNil(err) {
			return
		}
		errorHandler(ToWebContext(c), err)
	}
}

func (e *EchoListener) AddHandler(method, pattern string, handler component.RequestHandlerFunc, requestFilter ...component.RequestFilterFunc) {

	middlewares := make([]echo.MiddlewareFunc, len(requestFilter))

	for i, filterFunc := range requestFilter {
		middlewares[i] = RequestFilterAdapt(filterFunc).AdaptRequestFilterFunc
	}

	e.server.Add(strings.ToUpper(method), pattern, func(context echo.Context) error {
		if webContext, ok := context.Get(constant.RequestScopeContextKey).(component.WebContext); ok {
			return handler(webContext)
		} else {
			panic("The request is not one that the gateway can handle, Please check whether the request context implements ctx.WebContext.")
		}
	}, middlewares...)

	routers := e.server.Routes()
	for _, router := range routers {
		logger.Infow("add router", "method", router)
	}
}

func (e *EchoListener) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	e.server.ServeHTTP(rw, r)
}

func (e *EchoListener) SetNotFoundHandlerFunc(notFoundHandler component.RequestHandlerFunc) {
	e.server.RouteNotFound("/*", func(context echo.Context) error {
		if webContext, ok := context.Get(constant.RequestScopeContextKey).(component.WebContext); ok {
			return notFoundHandler(webContext)
		} else {
			panic("The request is not one that the gateway can handle, Please check whether the request context implements ctx.WebContext.")
		}
	})
	e.notFoundHandler = defaultNotFoundHandler
}

func (e *EchoListener) HandleNotFound(ctx component.WebContext) error {
	return e.notFoundHandler(ctx)
}

func (e *EchoListener) XRequestID(ctx interface{}) string {
	echoc, ok := ctx.(echo.Context)
	if !ok {
		panic("The request is not one that the gateway can handle, Please check whether the request context implements ctx.WebContext.")
	}
	requestId := echoc.Request().Header.Get(constant.XRequestID)
	if "" != requestId {
		return requestId
	}
	echoc.Request().Header.Set("X-RequestId-By", e.Name())
	return "HW-" + helper.Random(32, random.Lowercase, random.Numeric)
}

func (e *EchoListener) AddPreRequestFilters(filters ...component.PreRequestFilterFunc) {
	for _, filterFunc := range filters {
		e.server.Pre(PreRequestFilterFuncAdapt(filterFunc).AdaptPreRequestFilterFunc)
	}
}

func (e *EchoListener) AddPreRequestFilter(filter component.PreRequestFilterFunc) {
	e.server.Pre(PreRequestFilterFuncAdapt(filter).AdaptPreRequestFilterFunc)
}

func (e *EchoListener) OnInit() error {
	return nil
}

func (e *EchoListener) OnFinalize(ctx context.Context) error {
	return e.server.Shutdown(ctx)
}

func (e *EchoListener) Title() string {
	return e.name
}
